/**
 * Matrix.java
 *
 * Matrix class for representing and manipulating mathematical
 * m by n matrices of real (double) numbers.
 *
 * @ Authors:
 * @ original Wayne Witzel
 * @ modified for matlabMe by Leszek Piątek using some code (determinant, upperTrialange, adjoint) from
 * @ MatrixCalculator.java by Marcus Kazmierczak (marcus@mkaz.com, http://www.mkaz.com/math/)
 */

package matrix;
import java.io.*;

public class Matrix {
/* PRIVATE SECTION */
  private double[][] data;
  private int iDF = 0;

  private String round(double what) {
      String s = String.valueOf(what);
      int dot = s.indexOf(".");
      int e = s.indexOf("E");
      if(dot+5<s.length())
          if(e!=-1)
              return s.substring(0, dot+5)+s.substring(e,s.length());
          else
              return s.substring(0, dot+5);
      else
          return s;
  }
/* PUBLIC SECTION */
  
  //change new size for matrix - it have to be bigger (rows or cells)
  public void setNewSize(int row, int col) {
        if(this.cols()<col || this.rows()<row) {
            Matrix new_matrix = new Matrix(row, col);
            int i,j;
            for(i=0;i<rows();i++) {
                for(j=0;j<cols();j++) {
                    new_matrix.setCell(i, j, this.cell(i, j));
                }
            }
            this.data=new_matrix.data;
        }
    }

  // copy each cell from M to this, throwing an error
  // if M and this don't have the same dimensions.
  public void copy(Matrix M) throws MatrixDimensionError
  {
    if (M.rows() != rows() || M.cols() != cols())
      throw new MatrixDimensionError(this, M);

    subcopy(M);
  }

  // copy each cell from M to this (or a sub-matrix of
  // this with the dimensions of M), assuming M has
  // less then or equal number of rows and columns as this.
  public void subcopy(Matrix M) throws MatrixDimensionError
  {
    for (int i = 0; i < M.rows(); i++)
      for (int j = 0; j < M.cols(); j++)
        setCell(i, j, M.cell(i, j));
  }

  public int cols()  {
    return (data.length > 0) ? data[0].length : 0;
   
  }

  public int rows() {
     return data.length;
  }

  // return the cell
  public double cell(int row, int col)
  {
    return data[row][col];
  }

  // set the value of the cell at the ith row and jth column
  public void setCell(int row, int col, double value)
  {
      if((row<this.rows()) && (col>=this.cols())) {
            this.setNewSize(this.rows(), col+1);
      } else if((row>=this.rows()) && (col<this.cols()))  {
            this.setNewSize(row+1, this.cols());
      } else if ((row>=this.rows()) && (col>=this.cols())) {
            this.setNewSize(row+1, col+1);
      }
      data[row][col] = value;
  }

  /* Matrix addition */
  public Matrix add(Matrix M2) throws MatrixDimensionError
  {
    if (rows() != M2.rows() || cols() != M2.cols())
      throw new MatrixDimensionError(this, M2);

    Matrix result = new Matrix(rows(), cols());
    for (int i = 0; i < rows(); i++)
      for (int j = 0; j < cols(); j++)
        result.setCell(i, j, cell(i, j) + M2.cell(i, j));
    return result;
  }

  /*double to each element of Matrix addition */
  public void selfAdd(double a)
  {
    for (int i = 0; i < cols(); i++)
      for (int j = 0; j < rows(); j++)
        data[j][i] += a;
  }

  public static Matrix add(Matrix M1, Matrix M2) throws MatrixDimensionError
  { return M1.add(M2); }

  /* matrix subtraction */
  public Matrix subtract(Matrix M2) throws MatrixDimensionError
  {
    if (rows() != M2.rows() || cols() != M2.cols())
      throw new MatrixDimensionError(this, M2);

    Matrix result = new Matrix(rows(), cols());
    for (int i = 0; i < rows(); i++)
      for (int j = 0; j <  cols(); j++)
        result.setCell(i, j, cell(i, j) - M2.cell(i, j));
    return result;
  }

  public static Matrix subtract(Matrix M1, Matrix M2) throws MatrixDimensionError
  { return M1.subtract(M2); }

  /* scalar multiplication */
  public Matrix mult(double a)
  {
    Matrix result = new Matrix(rows(), cols());
    result.copy(this);
    result.selfMult(a);
    return result;
  }

  public void selfMult(double a)
  {
    for (int i = 0; i < cols(); i++)
      for (int j = 0; j < rows(); j++)
        data[j][i] *= a;
  }

  public void selfExp(double a)
  {
    for (int i = 0; i < cols(); i++)
      for (int j = 0; j < rows(); j++)
        data[j][i] = myjava.Math.pow(data[j][i], a);
  }

  public void selfDiv(double a)
  {
    for (int i = 0; i < cols(); i++)
      for (int j = 0; j < rows(); j++)
        data[j][i] /= a;
  }

  /* matrix multiplication */
  public Matrix mult(Matrix M2) throws MatrixDimensionError
  {
    if (cols() != M2.rows())
      throw new MatrixDimensionError(this, M2);

    Matrix result = new Matrix(M2.cols(), rows());
    for (int j = 0; j < result.rows(); j++) {
      for (int i = 0; i < result.cols(); i++) {
        double tmp = 0;
        for (int k = 0; k < cols(); k++)
          tmp += cell(k, j) * M2.cell(i, k);
        result.setCell(i, j, tmp);
      }
    }
    return result;
  }

  public static Matrix mult(Matrix M1, Matrix M2) throws MatrixDimensionError
  { return M1.mult(M2); }

  public Matrix transpose() {
		Matrix temp = new Matrix(this.cols(), this.rows());

		for(int i=0;i<this.rows();i++) {
			for(int j=0;j<this.cols();j++) {
				temp.data[j][i] = this.data[i][j];
			}
		}

		return temp;
  }
  
  public Matrix inverse() throws Exception {
      Matrix tmpMatrix = new Matrix(this);
      return this.inverse(tmpMatrix);
  }

  public Matrix inverse(Matrix a) throws Exception {
		// Formula used to Calculate inverse:
		// inv(A) = 1/det(A) * adj(A)
		if(a.rows()!=a.cols()) throw new Exception("Number of rows and columns are not equal!");
        int tms = a.rows();

		Matrix m = new Matrix(tms);
		Matrix mm = adjoint(a);

		double det = determinant(a);
		double dd = 0;

		if (det == 0) {
			throw new Exception("Determinant Equals 0, Not Invertible!");
		} else {
			dd = 1 / det;
		}

		for (int i = 0; i < tms; i++)
			for (int j = 0; j < tms; j++) {
				m.setCell(i, j, dd*mm.cell(i, j));
			}

		return m;
  }

  public Matrix adjoint(Matrix a) throws Exception {
		int tms = a.rows();

		Matrix m = new Matrix(tms);
		int ii, jj, ia, ja;
		double det;

		for (int i = 0; i < tms; i++)
			for (int j = 0; j < tms; j++) {
				ia = ja = 0;

				Matrix ap = new Matrix(tms-1);

				for (ii = 0; ii < tms; ii++) {
					for (jj = 0; jj < tms; jj++) {
						if ((ii != i) && (jj != j)) {
							ap.setCell(ia, ja, a.cell(ii, jj));
							ja++;
						}
					}
					if ((ii != i) && (jj != j)) {
						ia++;
					}
					ja = 0;
				}

				det = determinant(ap);
				m.setCell(i, j, (double)myjava.Math.pow(-1, i + j) * det);
			}
		return m.transpose();
  }

  public Matrix upperTriangle(Matrix m) {
		double f1 = 0;
		double temp = 0;
		int tms = m.rows(); // get This Matrix Size (could be smaller than
							// global)
		int v = 1;

		iDF = 1;

		for (int col = 0; col < tms - 1; col++) {
			for (int row = col + 1; row < tms; row++) {
				v = 1;

				outahere: while (m.cell(col,col) == 0) // check if 0 in diagonal
				{ // if so switch until not
					if (col + v >= tms) // check if switched all rows
					{
						iDF = 0;
						break outahere;
					} else {
						for (int c = 0; c < tms; c++) {
							temp = m.cell(col, c);
							m.setCell(col, c, m.cell(col+v, c)); // switch rows
							m.setCell(col+v, c, temp);
						}
						v++; // count row switchs
						iDF = iDF * -1; // each switch changes determinant
										// factor
					}
				}

				if (m.cell(col, col) != 0) {
					try {
						f1 = (-1) * m.cell(row, col) / m.cell(col, col);
						for (int i = col; i < tms; i++) {
                            m.setCell(row, i, f1*m.cell(col, i)+m.cell(row, i));
						}
					} catch (Exception e) {
						System.out.println("Still Here!!!");
					}
				}
			}
		}

		return m;
  }

  public double determinant() {
      Matrix tmpMatrix = new Matrix(this);
      return this.determinant(tmpMatrix);
  }

  public double determinant(Matrix matrix) {
		int tms = matrix.rows();

		double det = 1;

		matrix = upperTriangle(matrix);

		for (int i = 0; i < tms; i++) {
			det = det * matrix.cell(i,i);
		} // multiply down diagonal

		det = det * iDF; // adjust w/ determinant factor

		return det;
	}

  public void print(PrintStream out)
  {
    String ln;
    for (int j = 0; j < rows(); j++) {
      ln = "";
      for (int i = 0; i < rows(); i++)
        ln += cell(i, j) + "\t";
      out.println(ln);
    }
  }
  
  public String toString() {
		String string = "\n";
		for(int i=0;i<this.rows();i++) {
            string+="  ";
			for(int j=0;j<this.cols();j++) {
                //truncate to 4 decimal points while changing to string
                //string+=this.cell(i,j);
                string+=this.round(this.cell(i,j));
				//string+=(double)(int)((this.cell(i,j)+0.00005)*10000.0)/10000.0;
                if(j!=this.cols()-1) string+=", ";
			}
            string+="\n";
		}
       
		return string;
  }

  // create a square matrix initialized to the identity
  public Matrix(int dimension)
  {
    data = new double[dimension][dimension];
    for (int i = 0; i < dimension; i++)
      for (int j = 0; j < dimension; j++)
        data[i][j] = ((i==j) ? 1 : 0);
  }

  // create a matrix of any dimensions initialized
  // all cells = zeroes.
  public Matrix(int rows, int cols) {
    data = new double[rows][cols];
    for (int i = 0; i < rows; i++)
      for (int j = 0; j < cols; j++)
        data[i][j] = 0;
  }

  public Matrix(Matrix M) {
    data = new double[M.rows()][M.cols()];
    copy(M);
  }
}


